/**
 * jProton JavaScript Framework, version 0.4.0a
 * Copyright (c) 2008 Pedro Simonetti Garcia
 * 
 * Version - 0.4.0a
 * $Revision: 274 $
 * $Date: 2008-11-30 12:17:56 -0200 (dom, 30 nov 2008) $
 * 
 * Project site: http://code.google.com/p/jproton/
 * Licensed under MIT: http://www.opensource.org/licenses/mit-license.php
 */

/**
 * Namespace central do framework. "jProton" é um namespace executável, de modo
 * que ele é tanto um agregador de métodos/atributos quanto uma função. Como
 * namespace, ele é o agregador de todas funcionalidades do framework. Como
 * função, ele é um atalho para o principal recurso do framework: a seleção de
 * elementos do "documento". Além disso, o namespace "$" é um atalho para 
 * "jProton", de modo que os dois códigos abaixo:
 * 
 * ~~
 * // Usa o namespace jProton para instanciar a classe jProton.Set
 * jProton(selector, context)
 * // Usa o atalho $ para instanciar a classe jProton.Set
 * $(selector, context)
 * ~~
 * 
 * equivalem a:
 * 
 * ~~
 * // Instancia uma nova classe jProton.Set explicitamente 
 * new jProton.Set().select(selector, context)
 * ~~
 * 
 * A maioria dos recursos do framework encontram-se dentro de outros namespaces
 * dentro de "jProton", como "jProton.Set", ou "jProton.ajax". Mas, alguns 
 * recursos especiais encontram-se no próprio namespace "jProton", como os 
 * métodos "jProton.trim"() e "jProton.each"(), acessíveis também como 
 * "$.trim()" e "$.each()".
 *  
 * @name jProton
 * @namespace
 * @extends core
 *//** @ignore */
window.jProton = function(selector, context) {
  return new jProton.Set().select(selector, context);
};

/** 
 * Atalho para o namespace "jProton". Considerando que "jProton" é um 
 * namespace executável, ou seja, ele é um namespace e função ao mesmo tempo,
 * o atalho "$" pode ser usado para ambos os casos. Ou seja, é possível usá-lo 
 * como namespace para acessar recursos do framework, como a método "$.trim()", 
 * o namespace "$.ajax", ou a classe "$.Set". É possível também usá-lo como 
 * função, no formato "$()", o que consiste no atalho para instanciar uma 
 * classe "jProton.Set".
 * 
 * @function
 * @name $
 * @param {String}  selector  Seletor usado para selecionar os elementos.
 * @param {Element} [context] Contexto usado na busca. O padrão é "document".
 * @return {jProton.Set}
 * @see jProton
 * @extends core 
 */
window.$ = jProton;


/**
 * Cria um namespace, e retorna o último objeto desse namespace.
 * 
 * *Exemplo:*
 * ~~
 * // Cria o namespace 'my.name.space' caso não exista, e retorna o último 
 * // objeto do namespace, no caso o objeto 'space'.
 * $.namespace('my.name.space');
 * ~~
 * 
 * @function
 * @param {Object}  ns   Namespace a ser criado.
 */
jProton.namespace = function(ns, source) {
  if (ns) {
    var n = ns.split('.');
    var target = window, o;
    
    for (var i = 0, l = n.length-1, ni; (ni = n[i]) && (i < l); i++) {
      target = (o = target[ni]) ? o : target[ni] = {};
    }
    
    target = target[ni] = source || {};
    target.extend = jProton.extend;
  
    return target;
  } else {
    return false;
  }
};


/**
 * Extende as propriedades de um ou mais objetos-fonte para outro objeto-alvo. 
 * Se apenas um objeto for passado como parâmetro, ele será considerado o
 * objeto-fonte, e o objeto referenciado por "this" será considerado o alvo,
 * que nesse caso será o namespace "jProton". Se mais de um objeto for
 * passado como parâmetro, o primeiro objeto será considerado o alvo, e os 
 * objetos restantes serão considerados as fontes.
 * 
 * Ao final, será retornado o objeto-alvo extendido.
 * 
 * ===Sintaxe:===
 * ~~
 * jProton.extend( source || ( target, source1 [ , source2, ... ] ) );
 * ~~
 * 
 * Existem três propriedades que possuem significado especial quando 
 * definidas no objeto fonte. Elas são: "@init", "@extension" e "@require".
 * Essas propriedades não serão extendidas para o objeto alvo, servindo apenas
 * para definir respectivamente a inicialização, registro e dependência da 
 * extensão.
 * 
 * *NOTA: A maneira de declarar as extensões e suas dependências será 
 * diferente na versão 0.4.x, e por consequência, as propriedades
 * "@extension" e "@require" entrarão em desuso.*
 * 
 * ==Inicialização de Extensões==
 * 
 * Algumas extensões necessitam que um determinado código seja executado 
 * assim que a extensão for criada. Para isso, deve-se informar no objeto
 * fonte a ser inicializado a propriedade especial "@init", com a devida
 * função de inicialização como seu valor.
 * 
 * ==Registro de Extensões==
 * 
 * Além disso, o método "jProton.extend()" pode ser usado também para registrar 
 * extensões automaticamente. Para isso, deve-se informar uma propriedade 
 * especial no objeto fonte com nome "@extension". O valor dessa propriedade é 
 * um objeto que especifica o nome e versão da extensão, no seguinte formato:
 * 
 * ~~
 *   '@extension': {'name': 'version'}
 * ~~
 * 
 * O registro é feito definindo uma propriedade com nome "name" e valor 
 * "version" no namespace "extensions" do objeto alvo. Repare que se um
 * namespace for informado como alvo, a extensão será registrada no último
 * objeto do namespace, como exemplificado abaixo:
 * 
 * ~~
 *   target.namespace.extensions.name = 'version';
 * ~~
 * 
 * 
 * ==Dependência de Extensões==
 * 
 * Se a extensão que está sendo criada usa recursos de uma outra extensão,
 * deve-se usar a propriedade especial "@require" para a dependência com
 * outra extensão, evitando assim que a extensão seja carregada caso a
 * extensão requirida não esteja instalada.
 * 
 * Se a extensão requerida não estiver instalada, será gerado um erro 
 * indicando que a extensão deve ser instalada. Se a extensão requerida 
 * estiver instalada, mas for de uma versão inferior a versão requerida, 
 * será gerado um erro indicando a versão instalada é inferior a versão
 * requerida.
 * 
 * O exemplo a seguir ilustra o uso das três propriedades especiais para
 * se criar uma extensão de "jProton". Será criada a extensão "myExt" versão
 * "0.1" no namespace "jProton.myPlugin" apenas se a extensão "myOtherExt" 
 * versão "0.2" já estiver instalada em "jProton.myPlugin". Se a extensão
 * requerida estiver instalada, será criado o namespace "jProton.myPlugin", 
 * e adicionado a ele a propriedade "newProperty" e o método "newMethod". Além 
 * disso, como esta sendo informado a propriedade "@extension", será criada 
 * uma nova propriedade "jProton.myPlugin.extensions.myExt" com o valor "0.1". 
 * Ao final, será retornado o objeto alvo raiz, no caso, o objeto "jProton".
 * 
 * *Exemplo 2:*
 * ~~
 * // Cria o namespace onde do plugin
 * $.namespace('jProton.myPlugin');
 * 
 * // Cria uma nova extensão no namespace 'jProton.myPlugin'. Será criada
 * // uma nova propriedade 'jProton.myPlugin.extensions.myExt' com o valor
 * // '0.1' indicando o registro e versão da extensão.
 * $.extend(jProton.myPlugin, {
 * 
 *   // declara a dependência com outra extensão
 *   '@require': {'myOtherExt': '0.2'},
 *   
 *   // registra a extensão  
 *   '@extension': {'myExt': '0.1'},
 *   
 *   // Função de inicialização
 *   '@init': function() {
 *     // O código de inicialização segue aqui
 *     ...
 *   },
 *    
 *   // nova propriedade a ser extendida
 *   newProperty: 'value',
 *    
 *   // novo método a ser extendido
 *   newMethod: function() {
 *     return this.newProperty;
 *   }
 *   
 * });
 * ~~
 * 
 * @function
 * @param {Object} [target]  Objeto alvo para onde as propriedades serão
 *                           copiadas.
 * @param {Object}  source   Objeto fonte de onde as propriedades serão
 *                           copiadas.
 * @return {Object} Retorna o objeto "target" extendido.
 * @extends core
 * @see jProton.namespace
 */
jProton.extend = function() {
  var target = this, t = target, source, p, o,
      a0 = arguments[0], ai = 0, al = arguments.length;

  // processa o target
  if (al > 1) {
    ai++;
    target = t = a0;
  }

  // processa a extensão para cada objeto fonte
  for(; source = arguments[ai]; ai++)

    // para cada propriedade do objeto fonte
    for(var prop in source) {
      p = source[prop];
      
      // herda as propriedades do objeto fonte
      if (prop.charAt(0) != '@') {
        
        // se a propriedade for um array, extende esse array. Ou seja, se
        // já existir algum array na propriedade do objeto destino, concatena 
        // com o outro array. Se não existir, copia o array.
        if (p && p.splice) {
          var tp = t[prop];
          t[prop] = tp && tp.splice ? tp.concat(p) : new Array().concat(p);

        // se a propriedade for um objeto, extende esse objeto. Ou seja, se
        // já existir algum objeto na propriedade do objeto destino, extende o
        // objeto. Se não existir, cria uma cópia do objeto.
        } else if (p && typeof p == 'object') {
          t[prop] = jProton.extend(t[prop] || {}, p);

        // se for uma propriedade comum, copia seu valor
        } else {
          t[prop] = p;
        }
        
      } else {
        // processa a dependência de módulos
        if (prop == '@init') {
          p.call(t);
          
        // processa a dependência de módulos
        } else if (prop == '@require') {
          for (var ext in p) {

            // versão requerida
            var v = p[ext];
            
            // verifica se extensão requerida está instalada
            o = t.extensions[ext] || null;

            // confirma a existência e versão da extensão
            if (!o)
              throw 'A extensão "'+ext+'" deve estar instalada.';

            else if (o < v && v != 'all')
              throw 'A extensão "'+ext+'" deve ter a versão '+v+' ou superior.';
          }

        // processa o registro da extensão
        } else if (prop == '@extension') {

          // registra a extensão no namespace "extensions" do objeto
          o = t.extensions || (t.extensions = {});
          for (var name in p) {
            o[name] = p[name];
          }
        }
      }
    }
  // retorna o objeto alvo extendido
  return target;
};


// Extensão central de jProton.
/**#@+ @extends core */
jProton.extend(/** @lends jProton */ {

  '@extension': {'core': '0.3.0a'},

  /**
   * Atalho para a função "document.getElementById()".
   *  
   * @param {String} id
   * @return {Element}
   */
  id: function(id) {
    return document.getElementById(id);
  },

  /**
   * Executa uma função para cada elemento de um array.
   * 
   * ~~
   * // Exibe os elementos de um array
   * $.each(array, function(i,e) {
   *   alert('O elemento de número ' + i + ' é igual a ' + e);
   * });
   * ~~
   * 
   * @param {Array}    a   Array a ser percorrido.
   * @param {Function} fn  Função a ser chamada para cada elemento.
   */
  each: function(a, fn) {
    for(var i=0, ai; ai=a[i]; i++)
      fn.call(ai, i, ai);
  },

  /**
   * Remove os espaços laterais de uma string.
   * 
   * @param {String}  s  String a ser alterada.
   * @return {String}
   */
  trim: function(s) {
    return s.replace(/^\s*|\s*$/g, '');
  },

  /** 
   * Função vazia usada para remover funções adicionadas a elementos.
   */
  emptyFn: function() {}

  /**#@-*/
});
